/**
 * @author Jason Pratt
 */

package edu.cmu.cs.stage3.alice.authoringtool.util;

import edu.cmu.cs.stage3.alice.authoringtool.AuthoringTool;
import edu.cmu.cs.stage3.util.StringObjectPair;

public class ElementPrototype {

  protected Class elementClass;
  protected StringObjectPair[] knownPropertyValues;
  protected String[] desiredProperties;

  public ElementPrototype (Class elementClass, StringObjectPair[] knownPropertyValues, String[] desiredProperties) {

    if (! edu.cmu.cs.stage3.alice.core.Element.class.isAssignableFrom( elementClass )) {
      throw new IllegalArgumentException( "The elementClass given is not actually a subclass of Element." );
      }

    edu.cmu.cs.stage3.alice.core.Element testElement = null;
    try {
      testElement = (edu.cmu.cs.stage3.alice.core.Element)elementClass.newInstance();
    } catch( Exception e) {
      throw new IllegalArgumentException( "Unable to create a new element of type: " + elementClass.getName() );
      }
    if (! (edu.cmu.cs.stage3.alice.core.response.CallToUserDefinedResponse.class.isAssignableFrom( elementClass ) || edu.cmu.cs.stage3.alice.core.question.userdefined.CallToUserDefinedQuestion.class.isAssignableFrom( elementClass ))) { // don't do checking for CallToUserDefinedResponse/Question, since they use known and desired properties for requiredParameters
      if (knownPropertyValues != null) {
	//L 'downsize' to become compatible with older Java...
        //L for (StringObjectPair knownPropertyValue : knownPropertyValues)
        int t_tam = knownPropertyValues==null ? 0 : knownPropertyValues.length;
        for (int i_i=0; i_i<t_tam; i_i++) {
          String propertyName = knownPropertyValues[i_i].getString(); //L knownPropertyValue.getString()
          Object propertyValue = knownPropertyValues[i_i].getObject(); //L knownPropertyValue.getObject()
          edu.cmu.cs.stage3.alice.core.Property property = testElement.getPropertyNamed( propertyName );
          if (property == null) {
            throw new IllegalArgumentException( "property named " + propertyName + " does not exist in " + elementClass.getName() );
            }
          if (propertyValue == null) {
          } else if (propertyValue instanceof edu.cmu.cs.stage3.alice.core.Expression) {
            property.getValueClass();
            if (property.getValueClass().isAssignableFrom( propertyValue.getClass() )) {
              // allow
            } else {
              if (! property.getValueClass().isAssignableFrom( ((edu.cmu.cs.stage3.alice.core.Expression)propertyValue).getValueClass() )) {
                throw new IllegalArgumentException( "property named " + propertyName + " in class " + elementClass.getName() + " does not accept expressions of type " + ((edu.cmu.cs.stage3.alice.core.Expression)propertyValue).getValueClass().getName() );
                }
              }
          } else {
            if (! property.getValueClass().isAssignableFrom( propertyValue.getClass() )) {
              throw new IllegalArgumentException( "property named " + propertyName + " in class " + elementClass.getName() + " does not accept values of type " + propertyValue.getClass().getName() + "; bad value: " + propertyValue );
              }
            }
            }
      } else {
        knownPropertyValues = new StringObjectPair[0];
        }

      if (desiredProperties != null) {
	//L 'downsize' to become compatible with older Java...
        //L for (String desiredPropertie : desiredProperties)
        //L   edu.cmu.cs.stage3.alice.core.Property property = testElement.getPropertyNamed( desiredPropertie );
        int t_tam = desiredProperties==null ? 0 : desiredProperties.length;
        for (int i_i=0; i_i<t_tam; i_i++) {
          edu.cmu.cs.stage3.alice.core.Property property = testElement.getPropertyNamed( desiredProperties[i_i] );
          //T try {
          //T  if (property instanceof edu.cmu.cs.stage3.alice.core.property.StringProperty) {
          //T     edu.cmu.cs.stage3.alice.core.property.StringProperty strProperty = (edu.cmu.cs.stage3.alice.core.property.StringProperty)property;
          //T     System.out.println("ElementPrototype.ElementPrototype(...): "+strProperty.getStringValue());
          //T     }
          //T } catch (Exception e) {
          //T   System.out.println("ElementPrototype.ElementPrototype(...): "+property+": "+e.toString());
          //T 	} // e.printStackTrace();
          //L 'downsize' to became compatible with older Java...
          if (property == null) {
            throw new IllegalArgumentException( "property named " + desiredProperties[i_i] + " does not exist in " + elementClass.getName() );
            }
          }
      } else {
        desiredProperties = new String[0];
        }
      }

    this.elementClass = elementClass;
    this.knownPropertyValues = knownPropertyValues;
    this.desiredProperties = desiredProperties;
    }

  public edu.cmu.cs.stage3.alice.core.Element createNewElement () {
    try {
      edu.cmu.cs.stage3.alice.core.Element element = (edu.cmu.cs.stage3.alice.core.Element)elementClass.newInstance();

      if (knownPropertyValues != null) {
        //L 'downsize' to become compatible with older Java...
        //L for (StringObjectPair knownPropertyValue : knownPropertyValues)
        int t_tam = knownPropertyValues==null ? 0 : knownPropertyValues.length;
        for (int i_i=0; i_i<t_tam; i_i++) { //L
          String propertyName = knownPropertyValues[i_i].getString(); //L knownPropertyValue.getString();
          Object propertyValue = knownPropertyValues[i_i].getObject(); //L knownPropertyValue.getObject();
          edu.cmu.cs.stage3.alice.core.Property property = element.getPropertyNamed( propertyName );
          property.set( propertyValue );

          if (propertyValue instanceof edu.cmu.cs.stage3.alice.core.question.PropertyValue) { // this is pretty aggressive
            edu.cmu.cs.stage3.alice.core.question.PropertyValue propertyValueQuestion = (edu.cmu.cs.stage3.alice.core.question.PropertyValue)propertyValue;
            propertyValueQuestion.removeFromParent();
            property.getOwner().addChild( propertyValueQuestion );
            }
          else if ( propertyValue instanceof edu.cmu.cs.stage3.alice.core.Question) {
            edu.cmu.cs.stage3.alice.core.Question q = (edu.cmu.cs.stage3.alice.core.Question)propertyValue;
            q.removeFromParent();
            property.getOwner().addChild( q );
            //What the hell is this used for?
            q.data.put( "associatedProperty", property.getName() );
            }

          // HACK; is this too aggressive?
          if (propertyValue instanceof edu.cmu.cs.stage3.alice.core.Element) {
            edu.cmu.cs.stage3.alice.core.Element e = (edu.cmu.cs.stage3.alice.core.Element)propertyValue;
            if ((e.getParent() == null) && (! (e instanceof edu.cmu.cs.stage3.alice.core.World))) {
              property.getOwner().addChild( e );
              e.data.put( "associatedProperty", property.getName() );
            } 
          }
          // END HACK
         }
        }

      if (edu.cmu.cs.stage3.alice.core.response.ForEach.class.isAssignableFrom( elementClass )) {
        edu.cmu.cs.stage3.alice.core.response.ForEach forResponse = null;
        if (edu.cmu.cs.stage3.alice.core.response.ForEachInOrder.class.isAssignableFrom( elementClass )) {
          forResponse = (edu.cmu.cs.stage3.alice.core.response.ForEachInOrder)element;
          }
        edu.cmu.cs.stage3.alice.core.Variable eachVar = new edu.cmu.cs.stage3.alice.core.Variable();
        eachVar.name.set( "item" );
        eachVar.valueClass.set( Object.class );
        if(forResponse != null){
          forResponse.addChild( eachVar );
          forResponse.each.set( eachVar );
          }
      } else if (edu.cmu.cs.stage3.alice.core.question.userdefined.ForEach.class.isAssignableFrom( elementClass )) {
        edu.cmu.cs.stage3.alice.core.question.userdefined.ForEach forQuestion = (edu.cmu.cs.stage3.alice.core.question.userdefined.ForEach)element;
        edu.cmu.cs.stage3.alice.core.Variable eachVar = new edu.cmu.cs.stage3.alice.core.Variable();
        eachVar.name.set( "item" );
        eachVar.valueClass.set( Object.class );
        forQuestion.addChild( eachVar );
        forQuestion.each.set( eachVar );
      } else if (edu.cmu.cs.stage3.alice.core.response.LoopNInOrder.class.isAssignableFrom( elementClass ) ){
        edu.cmu.cs.stage3.alice.core.response.LoopNInOrder loopN = (edu.cmu.cs.stage3.alice.core.response.LoopNInOrder)element;
        edu.cmu.cs.stage3.alice.core.Variable indexVar = new edu.cmu.cs.stage3.alice.core.Variable();
        indexVar.name.set( "index" );
        indexVar.valueClass.set( Number.class );
        loopN.addChild( indexVar );
        loopN.index.set( indexVar );
      } else if (edu.cmu.cs.stage3.alice.core.question.userdefined.LoopN.class.isAssignableFrom( elementClass ) ){
        edu.cmu.cs.stage3.alice.core.question.userdefined.LoopN loopN = (edu.cmu.cs.stage3.alice.core.question.userdefined.LoopN)element;
        edu.cmu.cs.stage3.alice.core.Variable indexVar = new edu.cmu.cs.stage3.alice.core.Variable();
        indexVar.name.set( "index" );
        indexVar.valueClass.set( Number.class );
        loopN.addChild( indexVar );
        loopN.index.set( indexVar );
        }
      return element;
    } catch( Exception e) {
      AuthoringTool.getInstance().showErrorDialog( "Error creating new element.", e ); // should not be reached if we did adequate checking in Constructor.
      }

    return null;
    }

  public ElementPrototype createCopy( StringObjectPair newKnownPropertyValue) {
    return createCopy( new StringObjectPair[] { newKnownPropertyValue } );
    }

  public ElementPrototype createCopy (StringObjectPair[] newKnownPropertyValues) {
    java.util.Vector vKnownPropertyValues = new java.util.Vector( java.util.Arrays.asList( knownPropertyValues ) );
    java.util.Vector vDesiredProperties = new java.util.Vector( java.util.Arrays.asList( desiredProperties ) );

    if (newKnownPropertyValues != null) {
      //L 'downsize' to become compatible with older Java...
      //L for (StringObjectPair newKnownPropertyValue : newKnownPropertyValues) 
      int t_tam = newKnownPropertyValues==null ? 0 : newKnownPropertyValues.length; //L
      for (int i_i=0; i_i<t_tam; i_i++) { //L

        if (vDesiredProperties.contains( newKnownPropertyValues[i_i].getString() )) { //L newKnownPropertyValue.getString()
           vDesiredProperties.remove( newKnownPropertyValues[i_i].getString() ); //L newKnownPropertyValue.getString()
           }
        boolean subbed = false;
        for( java.util.ListIterator iter = vKnownPropertyValues.listIterator(); iter.hasNext();) {
          StringObjectPair pair = (StringObjectPair)iter.next();
          if (pair.getString().equals( newKnownPropertyValues[i_i].getString() )) { //L newKnownPropertyValue.getString()
            iter.set( newKnownPropertyValues[i_i] ); //L newKnownPropertyValue
            subbed = true;
            }
          }
        if (! subbed) {
           vKnownPropertyValues.add( newKnownPropertyValues[i_i] ); //L vKnownPropertyValues.add( newKnownPropertyValue )
           }
        }
      } // if (newKnownPropertyValues != null)

    return createInstance( elementClass, (StringObjectPair[])vKnownPropertyValues.toArray( new StringObjectPair[0] ), (String[])vDesiredProperties.toArray( new String[0] ) );
    }

  public Class getElementClass () {
    return elementClass;
    }

  public StringObjectPair[] getKnownPropertyValues () {
    return knownPropertyValues;
    }

  public String[] getDesiredProperties () {
    return desiredProperties;
    }

  // a rather inelegant solution for creating copies of the correct type.
  // subclasses should overrider this method and call their own constructor
  protected ElementPrototype createInstance( Class elementClass, StringObjectPair[] knownPropertyValues, String[] desiredProperties) {
    return new ElementPrototype( elementClass, knownPropertyValues, desiredProperties );
    }

  //Rem @Override
  public String toString () {
    StringBuffer sb = new StringBuffer();
    sb.append( this.getClass().getName() + "[ " );
    sb.append( "elementClass = " + elementClass.getName() + ", " );
    sb.append( "knownPropertyValues = [ " );
    if (knownPropertyValues != null) {
      //L 'downsize' to become compatible with older Java...
      //L for (StringObjectPair knownPropertyValue : knownPropertyValues)
      int t_tam = knownPropertyValues==null ? 0 : knownPropertyValues.length; //L
      for (int i_i=0; i_i<t_tam; i_i++) { //L
        //sb.append( knownPropertyValues[_i].toString() + ", " );
        sb.append( "StringObjectPair( " );
        sb.append( knownPropertyValues[i_i].getString() + ", " ); //L knownPropertyValue.getString()
        sb.append( knownPropertyValues[i_i].getObject().toString() + ", " ); //L knownPropertyValue.getObject()
        sb.append( "), " );
        }
    } else {
      sb.append( "<null>" );
      }
    sb.append( " ], " );
    sb.append( "desiredProperties = [ " );
    if (desiredProperties != null) {

      //L 'downsize' to become compatible with older Java...
      //L for (String desiredPropertie : desiredProperties) 
      String [] desiredPropertie = desiredProperties; //L
      int tam = desiredPropertie==null ? 0 : desiredPropertie.length; //L
      for (int _i=0; _i<tam; _i++) { //L
        sb.append( desiredPropertie[_i] + ", " ); //L desiredPropertie
        }
    } else {
      sb.append( "<null>" );
      }
    sb.append( " ], ]" );

    return sb.toString();
    }

  }
